22. Exercise: Recording Sleep Quality
L6 43 Navigate SC
Update: In the video above, inside
onCreateView()function ofSleepTrackerFragment.ktfile, the first argument in thesleepTrackerViewModel.navigateToSleepQuality.observe()function would beviewLifecycleOwnerinstead of the current objectthis.
L6 44 Record The Sleep Quality SC V2
Update:
In the video above, in theSleepQualityViewModel.ktfile, creating own scopeuiScopeis no longer recommended by Google. The recommended way is to use a lifecycle-aware coroutine scope ViewModelScope, provided by the Architecture components. Therefore, we will not require theviewModelJobobject,uiScopecoroutine, and theonCleared()function.
As a result, in theonSetSleepQuality()function, launching a coroutine must be done inside aviewModelScope, instead ofuiScope.
These updates are similar to what you must have done inSleepTrackerViewModel.ktfile previously.
Now it's your turn to complete this exercise yourself.
In this step, you'll add navigation and sleep quality recording to your app.
Navigation
Inspect the provided
SleepQualityFragment.kt.Inspect the provided
fragment_sleep_quality.xml.Open
navigation.xmland inspect the code.In particular, look for the
<argument>where we are passing asleepNightKeyfrom theSleepTrackerFragmentto theSleepQualityFragment.Open
SleepTrackerViewModel.kt.You need to add Navigation, so that when the user taps the STOP button, you navigate to the
SleepQualityFragmentto collect a quality rating.In
SleepTrackerViewModel.kt, inonStopTracking()set aLiveDatathat changes when you want to navigate.Use encapsulation to expose only a gettable version to the fragment:
private val _navigateToSleepQuality = MutableLiveData<SleepNight>()
val navigateToSleepQuality: LiveData<SleepNight>
get() = _navigateToSleepQuality
- Add a
doneNavigating()function that resets the event.
fun doneNavigating() {
_navigateToSleepQuality.value = null
}
- In the click handler for the STOP button,
onStopTracking(), trigger this navigation:
_navigateToSleepQuality.value = oldNight
- In the
SleepTrackerFragment, inonCreateView(), add an observer fornavigateToSleepQuality.
sleepTrackerViewModel.navigateToSleepQuality.observe(viewLifecycleOwner, Observer {
})
- Inside the observer block, navigate and pass along the ID of the current
night, and then calldoneNavigating():
night ->
night?.let {
this.findNavController().navigate(
SleepTrackerFragmentDirections
.actionSleepTrackerFragmentToSleepQualityFragment(night.nightId))
sleepTrackerViewModel.doneNavigating()
}
Build and run your app. Click START, then click STOP, which should take you to the SleepQualityFragment screen.
To get back, use the Back button.
Record the Sleep Quality
In the
sleepqualitypackage, openSleepQualityViewModel.kt.Create a
SleepQualityViewModelthat takessleepNightKeyanddatabaseas arguments:
class SleepQualityViewModel(
private val sleepNightKey: Long = 0L,
val database: SleepDatabaseDao) : ViewModel() {
}
- To navigate back to the
SleepTrackerFragment, analogously implementnavigateToSleepTrackerand_navigateToSleepTracker, as well asdoneNavigating():
private val _navigateToSleepTracker = MutableLiveData<Boolean?>()
val navigateToSleepTracker: LiveData<Boolean?>
get() = _navigateToSleepTracker
fun doneNavigating() {
_navigateToSleepTracker.value = null
}
**Now, create one click handler that you will use for all the smiley sleep quality images,
onSetSleepQuality(). **Use the same coroutine pattern. Launch a coroutine in
uiScope, switch to the IO dispatcher, gettonightusing thesleepNightKey, set the sleep quality, update the database, and trigger navigation:
fun onSetSleepQuality(quality: Int) {
viewModelScope.launch {
val tonight = database.get(sleepNightKey) ?: return@withContext
tonight.sleepQuality = quality
database.update(tonight)
_navigateToSleepTracker.value = true
}
}
Create a
SleepQualityViewModelFactory.Since this is a version of the same boilerplate code, here is the code. Inspect the code before you move on:
class SleepQualityViewModelFactory(
private val sleepNightKey: Long,
private val dataSource: SleepDatabaseDao) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(SleepQualityViewModel::class.java)) {
return SleepQualityViewModel(sleepNightKey, dataSource) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
Open
SleepQualityFragment.Get the
arguments:
val arguments = SleepQualityFragmentArgs.fromBundle(arguments!!)
- Get the
dataSource:
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
- And create a factory passing in the
dataSourceandsleepNightKey:
val viewModelFactory = SleepQualityViewModelFactory(arguments.sleepNightKey, dataSource)
- Get a
SleepQualityViewModelreference:
val sleepQualityViewModel =
ViewModelProvider(
this, viewModelFactory).get(SleepQualityViewModel::class.java)
- Add the
sleepQualityViewModelto thebindingobject:
binding.sleepQualityViewModel = sleepQualityViewModel
- And add the observer, as before:
sleepQualityViewModel.navigateToSleepTracker.observe(viewLifecycleOwner, Observer {
if (it == true) { // Observed state is true.
this.findNavController().navigate(
SleepQualityFragmentDirections.actionSleepQualityFragmentToSleepTrackerFragment())
sleepQualityViewModel.doneNavigating()
}
})
- Open
fragment_sleep_quality.xmland add a variable for theSleepQualityViewModelto the<data>block:
<data>
<variable
name="sleepQualityViewModel"
type="com.example.android.trackmysleepquality.sleepquality.SleepQualityViewModel" />
</data>
Add a click handler like the one below to each image.
Match the quality rating to the image.
android:onClick="@{() -> sleepQualityViewModel.onSetSleepQuality(5)}"
- Clear cache, rebuild your app, and make sure it runs without errors.
If you want to start at this step, you can download this exercise from: Step.06-Exercise-Record-SleepQuality.
You will find plenty of //TODO comments to help you complete this exercise, and if you get stuck, go back and watch the video again.
Once you’re done, you can check your solution against the solution we’ve provided here: Step.06-Solution-Record-SleepQuality, or using this git diff.
Task Feedback:
Congratulations! You've just built a complete Room database app, using coroutines, and applied just about everything you've learned in the previous lessons!
Reference Documentation